package de.tum.in.eist.im.client;

import java.util.List;

import com.google.gwt.core.client.*;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.*;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;

import de.tum.in.eist.im.client.domain.*;
import de.tum.in.eist.im.client.view.*;

/**
 * Represents the main handler holding delegates and controlling the contents of
 * the {@link ClientModel} and pushes notifications and message to both the
 * {@link ClientModel} and the {@link ClientView}.
 * 
 * @author Christian Ivicevic (ga73yis@mytum.de)
 * @since 08-31-2011
 * @version 09-02-2011
 */
public class ClientController {
	/**
	 * Exposes asynchronous functions to call the server component via RPC.
	 */
	public static final ChatServiceAsync chatSvc = GWT
			.create(ChatService.class);
	/**
	 * Exposes functions to manipulate the {@link ClientModel} object which the
	 * {@link ClientView} observes.
	 */
	private static final ClientController singleton = new ClientController();

	/**
	 * Represents the model associated with the {@link ClientController} object.
	 */
	private ClientModel theModel = null;
	/**
	 * Represents the view associated with the {@link ClientController} object.
	 */
	private ClientView theView = null;

	/**
	 * Creates a new {@link ClientController} object and instantiates both the
	 * {@link ClientModel} and the {@link ClientView}.
	 */
	private ClientController() {
		this.theModel = new ClientModel();
		this.theView = new ClientView(this.theModel);

		// Registers the event handler which does the automated logout
		Window.addWindowClosingHandler(new Window.ClosingHandler() {
			/**
			 * Prevents the user from closing the window by asking him whether
			 * he wants to.
			 * 
			 * @param event
			 */
			@Override
			public void onWindowClosing(ClosingEvent event) {
				if (ClientController.this.theModel.isLoggedIn()
						&& ClientController.this.theModel.getCurrentUser() != null)
					event.setMessage("If you leave this page, your EIST_IM sessions will not be preserved!");
			}
		});

		// Registers the event handler which does the automated logout
		Window.addCloseHandler(new CloseHandler<Window>() {
			/**
			 * Logs out the user if he is currently logged and closes the
			 * window.
			 * 
			 * @param event
			 */
			@Override
			public void onClose(CloseEvent<Window> event) {
				if (ClientController.this.theModel.isLoggedIn()
						&& ClientController.this.theModel.getCurrentUser() != null) {

					// Locks the render function so that the onSuccess of
					// the logout function does not weird things
					try {
						ClientController.this.theView.requestLock();
						System.out
								.println("[Window::onClose] Trying to automatically logout the user.");

						synchronized (new Object()) {
							ClientController.this
									.logoutUser(ClientController.this.theModel
											.getCurrentUser());
						}
					} catch (Exception ex) {
						System.err.println("[ERROR] " + ex.getMessage());
					}
				}
			}
		});
	}

	/**
	 * Gets the current instance of the {@link ClientController} class.
	 * 
	 * @return
	 */
	public static final ClientController get() {
		return ClientController.singleton;
	}

	/**
	 * Loads the specified {@link IView} object and forces the
	 * {@link ClientView} to render it.
	 * 
	 * @param view
	 *            the {@link IView} object to load.
	 */
	public void loadView(IView view) {
		view.create(this.theModel);
		this.theView.render(view);
	}

	/**
	 * Pushes the new online {@link User}s list to both the {@link ClientModel}
	 * and notifies the {@link ClientView} to update itself.
	 * 
	 * @param userList
	 *            the new list of {@link User}s who are currently online.
	 */
	public void pushNewUserList(List<User> userList) {
		this.theModel.pushNewUserList(userList);
		this.theView.render();
	}

	/**
	 * Tries to login the specified user and updates the {@link ClientView}
	 * object to either an error message or the chat interface.
	 * 
	 * @param user
	 *            {@link User} object representing the user to login
	 */
	public void loginUser(final User user) {
		// Execute the login protocol
		ClientController.chatSvc.login(user, new AsyncCallback<Boolean>() {
			/**
			 * The request was successfully executed. Returns a boolean value
			 * indicating whether the user was logged in.
			 * 
			 * @param result
			 *            true, if the user was logged in; otherwise, false.
			 */
			@Override
			public void onSuccess(Boolean result) {
				// Get the info label - we expect here to have loaded the
				// LoginView...
				RootPanel infoLabel = RootPanel.get("infoLabel");
				if (result) {
					// The user was successfully logged in and we must both
					// store him in the model and then update the view.
					// Log this success and show success message for debug
					// purposes
					System.out
							.println("[ClientController::loginUser] Login succeeded for User "
									+ user.getUsername() + ".");
					ClientController.this.theModel.setCurrentUser(user);
					ClientController.this.theModel.setLoggedIn(true);
					ClientController.this.theView.render(new ChatView());
				} else {
					// Log this error and show error label
					System.out
							.println("[ClientController::loginUser] Login failed for User "
									+ user.getUsername() + "!");
					infoLabel.setStyleName("infoLabel", false);
					infoLabel.setStyleName("errorLabel", true);
					infoLabel
							.getElement()
							.setInnerText(
									"Login failed: Either you are not registered or you have entered wrong credentials.");
					infoLabel.setVisible(true);
				}
			}

			/**
			 * The request provoked an error.
			 */
			@Override
			public void onFailure(Throwable up) {
				try {
					throw up; // Ha ha
				} catch (Throwable e) {
					// Who cares?
				}
			}
		});
	}

	/**
	 * Tries to logout the specified user and updates the {@link ClientView}
	 * object to either an error message or the login.
	 * 
	 * @param user
	 *            {@link User} object representing the user to logout
	 */
	public void logoutUser(final User user) {
		// Execute the logout protocol
		ClientController.chatSvc.logout(user, new AsyncCallback<Boolean>() {
			/**
			 * The request was successfully executed. Returns a boolean value
			 * indicating whether the user was logged out.
			 * 
			 * @param result
			 *            true, if the user was logged out; otherwise, false.
			 */
			@Override
			public void onSuccess(Boolean result) {
				System.out.println("[ClientController::logoutUser] Logout "
						+ (result ? "succeeded" : "failed") + " for User "
						+ user.getUsername() + ".");
				ClientController.this.theModel.setLoggedIn(false);
				if (result)
					ClientController.this.theView.render(new LoginView());
			}

			/**
			 * The request provoked an error.
			 */
			@Override
			public void onFailure(Throwable up) {
				try {
					throw up; // Ha ha
				} catch (Throwable e) {
					// Who cares?
				}
			}
		});
	}
}